use std::{iter::Peekable, slice::Iter};

use nand2tetris_vmtranslator_rust::data::{ArithmeticCommand, Segment};

use crate::{
    compiler::{
        ProgramCompilerUnit,
        data::{ArithmeticOperator, KeywordConst},
    },
    data::{Identifier, IntegerConstant, KeywordType, SymbolType},
    error::{CompilerError, CompilerResult},
    token::Token,
};

/// Expression compiler unit
pub(super) struct ExpressionCompilerUnit;

impl ExpressionCompilerUnit {
    /// The compiler flow unit of expression list
    pub(super) fn compile_expression_list(
        compiler: &mut ProgramCompilerUnit,
        tokens: &mut Peekable<Iter<'_, Token>>,
    ) -> CompilerResult<u16> {
        let mut num_args: u16 = 0;
        if tokens.peek() != Some(&&Token::Symbol(SymbolType::CloseParenthesis)) {
            Self::compile_expression(compiler, tokens)?;
            num_args += 1;
            while let Some(Token::Symbol(SymbolType::Comma)) = tokens.peek() {
                // skip ,
                let _ = tokens.next();
                Self::compile_expression(compiler, tokens)?;
                num_args += 1;
            }
        }
        Ok(num_args)
    }
    /// The compiler flow unit of expression
    pub(super) fn compile_expression(
        compiler: &mut ProgramCompilerUnit,
        tokens: &mut Peekable<Iter<'_, Token>>,
    ) -> CompilerResult<()> {
        Self::compile_term(compiler, tokens)?;

        while let Some(token) = tokens.peek() {
            if let Ok(op) = Self::flow_unit_op(token) {
                // skip op
                let _ = tokens.next();
                Self::compile_term(compiler, tokens)?;
                compiler.vm_writer.write_arithmetic_operator(op);
            } else {
                break;
            }
        }

        Ok(())
    }
    /// The compiler flow unit of subroutine call
    ///
    /// # arguments
    ///
    /// * tokens the compiler tokens
    /// * first_identifier 因term需要判断是否varname还是subroutineCall，前序已经提取了第一个token
    pub(super) fn compile_subroutine_call(
        compiler: &mut ProgramCompilerUnit,
        tokens: &mut Peekable<Iter<'_, Token>>,
        first_identifier: Option<Identifier>,
    ) -> CompilerResult<()> {
        let first_identifier = if let Some(first_identifier) = first_identifier {
            first_identifier
        } else {
            Self::flow_unit_identifier(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::SubroutineCall("miss subroutineName".into()))?,
            )?
        };
        match tokens.next() {
            Some(Token::Symbol(SymbolType::OpenParenthesis)) => {
                // subroutineName(expressionList)
                // equal to this.subroutineName(expressionList)
                let num_args = 1 + Self::compile_expression_list(compiler, tokens)?;
                let _ = crate::analyzer::Analyzer::match_symbol(
                    tokens
                        .next()
                        .ok_or_else(|| CompilerError::SubroutineCall("miss )".into()))?,
                    SymbolType::CloseParenthesis,
                )?;
                // push this to arguments
                compiler.vm_writer.write_push(Segment::This, 0);
                // generate code for subroutine call
                compiler.vm_writer.write_call(
                    compiler.general_subroutine_name(&first_identifier),
                    num_args,
                );
            }
            Some(Token::Symbol(SymbolType::Dot)) => {
                //className|varName.subroutineName(expressionList)

                if let Ok(subroutine_name) =
                    Self::flow_unit_identifier(tokens.next().ok_or_else(|| {
                        CompilerError::SubroutineCall("miss subroutineName".into())
                    })?)
                {
                    let _ = crate::analyzer::Analyzer::match_symbol(
                        tokens
                            .next()
                            .ok_or_else(|| CompilerError::SubroutineCall("miss (".into()))?,
                        SymbolType::OpenParenthesis,
                    )?;
                    let (called_subroutine_name, mut num_args) = if let Some(var_name) =
                        compiler.get_variable(first_identifier.as_str())
                    {
                        // It's a method call on a variable
                        // varName.subroutineName(expressionList)
                        // push the variable to the stack as this
                        let subroutine_name = format!(
                            "{}.{}",
                            var_name.get_class_name().unwrap_or("this"),
                            subroutine_name.as_str()
                        );
                        // push the variable to the stack as this
                        compiler
                            .vm_writer
                            .write_push(var_name.var_kind.into(), var_name.index_of_kind);
                        (subroutine_name, 1)
                    } else {
                        let subroutine_name =
                            format!("{}.{}", first_identifier.as_str(), subroutine_name.as_str());
                        (subroutine_name, 0)
                    };

                    num_args += Self::compile_expression_list(compiler, tokens)?;

                    let _ = crate::analyzer::Analyzer::match_symbol(
                        tokens
                            .next()
                            .ok_or_else(|| CompilerError::SubroutineCall("miss )".into()))?,
                        SymbolType::CloseParenthesis,
                    )?;
                    // generate code for subroutine call
                    compiler
                        .vm_writer
                        .write_call(called_subroutine_name, num_args);
                } else {
                    return Err(CompilerError::SubroutineCall("miss subroutine name".into()));
                }
            }
            _ => return Err(CompilerError::SubroutineCall("miss ( or .".into())),
        }
        Ok(())
    }
    /// The compiler flow unit of term
    fn compile_term(
        compiler: &mut ProgramCompilerUnit,
        tokens: &mut Peekable<Iter<'_, Token>>,
    ) -> CompilerResult<()> {
        let token = tokens
            .peek()
            .cloned()
            .ok_or_else(|| CompilerError::Term("no token".into()))?;

        if let Ok(IntegerConstant(value)) = Self::flow_unit_int_const(token) {
            // case 1 : integer const
            let _ = tokens.next();
            compiler.vm_writer.write_push(Segment::Constant, value);
        } else if let Ok(keyword_const) = Self::flow_unit_keyword_const(token) {
            // case 2 : true false null this
            // skip the first peek
            let _ = tokens.next();
            compiler.vm_writer.write_keyword_const(keyword_const);
        } else if let Ok(string) = Self::flow_unit_string_const(token) {
            // case 3 : string const
            // skip the first peek
            let _ = tokens.next();
            compiler.vm_writer.write_string_const(string);
        } else if let Token::Symbol(SymbolType::OpenParenthesis) = token {
            // case 4 : (expression)
            // skip (
            let _ = tokens.next();
            Self::compile_expression(compiler, tokens)?;
            let _ = crate::analyzer::Analyzer::match_symbol(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::Term("miss )".into()))?,
                SymbolType::CloseParenthesis,
            )?;
        } else if let Ok(unary_opr) = Self::flow_unit_unary_op(token) {
            // case 5 : unary operator
            // skip unary_opr
            let _ = tokens.next();
            // unaryOp term
            if unary_opr == ArithmeticOperator::Minus {
                // convert -1 to  0 - 1
                compiler.vm_writer.write_push(Segment::Constant, 0);
            }
            // get the term value,it will push a result on the stack top
            Self::compile_term(compiler, tokens)?;
            // do the opr
            compiler.vm_writer.write_arithmetic_operator(unary_opr);
        } else if let Ok(var_name) = Self::flow_unit_identifier(token) {
            // case 6 : array
            // skip varName
            let _ = tokens.next();
            // maybe varName | subroutineCall
            match tokens.peek() {
                Some(Token::Symbol(SymbolType::OpenBracket)) => {
                    // varname[expression]
                    // arr[expression]
                    let var_name = compiler
                        .get_variable(var_name.as_str())
                        .ok_or_else(|| {
                            CompilerError::Indentifier(format!(
                                "can'nt found the variable : {}",
                                var_name.as_str()
                            ))
                        })
                        .map(|item| item.to_owned())?;
                    // skip [
                    let _ = tokens.next();
                    // step 1 : push the expression value to the top of stack
                    Self::compile_expression(compiler, tokens)?;
                    // step 2 : push arr
                    compiler
                        .vm_writer
                        .write_push(var_name.var_kind.into(), var_name.index_of_kind);
                    // arr + expression
                    // write add
                    compiler
                        .vm_writer
                        .write_arithmetic_command(ArithmeticCommand::Add);
                    // set temp_0 = arr+expression
                    compiler.vm_writer.write_pop(Segment::Pointer, 1);
                    // read the temp_0's value to the top of stack
                    compiler.vm_writer.write_push(Segment::That, 0);

                    // ]
                    let _ = crate::analyzer::Analyzer::match_symbol(
                        tokens
                            .next()
                            .ok_or_else(|| CompilerError::Term("miss ]".into()))?,
                        SymbolType::CloseBracket,
                    )?;
                }
                Some(Token::Symbol(symbol))
                    if [SymbolType::OpenParenthesis, SymbolType::Dot].contains(symbol) =>
                {
                    // subroutine call
                    Self::compile_subroutine_call(compiler, tokens, Some(var_name))?;
                }
                _ => {
                    let var_name = compiler
                        .get_variable(var_name.as_str())
                        .ok_or_else(|| {
                            CompilerError::Indentifier(format!(
                                "can'nt found the variable : {}",
                                var_name.as_str()
                            ))
                        })
                        .map(|item| item.to_owned())?;
                    // push variable value to stack
                    compiler
                        .vm_writer
                        .write_push(var_name.var_kind.into(), var_name.index_of_kind);
                }
            }
        } else {
            return Err(CompilerError::Term("no a term".into()));
        }

        Ok(())
    }
    /// The compiler flow unit of int const
    pub(super) fn flow_unit_int_const(token: &Token) -> CompilerResult<IntegerConstant> {
        if let Token::IntConst(int_const) = token {
            Ok(int_const.to_owned())
        } else {
            Err(CompilerError::IntConst(token.to_xml()))
        }
    }

    /// The compiler flow unit of int const
    fn flow_unit_string_const(token: &Token) -> CompilerResult<String> {
        if let Token::StringCost(string) = token {
            Ok(string.to_owned())
        } else {
            Err(CompilerError::StringConst(token.to_xml()))
        }
    }

    /// The compiler flow unit of identifier
    pub(super) fn flow_unit_identifier(token: &Token) -> CompilerResult<Identifier> {
        if let Token::Identifier(identifier) = token {
            Ok(identifier.to_owned())
        } else {
            Err(CompilerError::Indentifier(token.to_xml()))
        }
    }
    /// The compiler flow unit of keyword const
    fn flow_unit_keyword_const(token: &Token) -> CompilerResult<KeywordConst> {
        if let Token::Keyword(keyword) = token
            && [
                KeywordType::True,
                KeywordType::False,
                KeywordType::Null,
                KeywordType::This,
            ]
            .contains(keyword)
        {
            keyword.to_owned().try_into()
        } else {
            Err(CompilerError::Keyword(token.to_xml()))
        }
    }

    /// The compiler flow unit of unary opertor
    fn flow_unit_unary_op(token: &Token) -> CompilerResult<ArithmeticOperator> {
        if let Token::Symbol(symbol) = &token
            && [SymbolType::Minus, SymbolType::Not].contains(symbol)
        {
            symbol.to_owned().try_into()
        } else {
            Err(CompilerError::Operator("not a unary operator".to_string()))
        }
    }

    /// The compiler flow unit of opertor : + - * / & < > =
    fn flow_unit_op(token: &Token) -> CompilerResult<ArithmeticOperator> {
        if let Token::Symbol(symbol) = token
            && [
                SymbolType::Plus,
                SymbolType::Minus,
                SymbolType::Multiply,
                SymbolType::Divide,
                SymbolType::And,
                SymbolType::Or,
                SymbolType::LessThan,
                SymbolType::GreaterThan,
                SymbolType::Equal,
            ]
            .contains(symbol)
        {
            symbol.to_owned().try_into()
        } else {
            Err(CompilerError::Operator("not a operator".into()))
        }
    }
}
